#include "portmacro.h"
#include "projdefs.h"
#include "task.h"
#include "ARMCM3.h"
#include "list.h"

static UBaseType_t uxCriticalNesting = 0xaaaaaaaa;

/*
*************************************************************************
*                              宏定义
*************************************************************************
*/

#define portINITIAL_XPSR               ( 0x01000000 )
#define portSTART_ADDRESS_MASK         ( (StackType_t) 0xfffffffeUL)

/*
 *  参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.7，百度搜索“PM0056”即可找到这个文档
 *  在 Cortex-M 中，内核外设 SCB 中 SHPR3 寄存器用于设置 SysTick 和 PendSV 的异常优先级
 *  System handler priority register 3 (SCB_SHPR3) SCB_SHPR3：0xE000 ED20
 *  Bits 31:24 PRI_15[7:0]: Priority of system handler 15, SysTick exception
 *  Bits 23:16 PRI_14[7:0]: Priority of system handler 14, PendSV
 */
#define portNVIC_SYSPRI2_REG  (*(( volatile uint32_t *) 0xe000ed20))                      /* SHPR3 寄存器的基地址 */
#define portNVIC_PENDSV_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 16UL)       /* PendSV  的优先级设置为最低 */
#define portNVIC_SYSTICK_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )     /* SysTick 的优先级设置为最低 */

#define portNVIC_SYSTICK_CTRL_REG (*((volatile uint32_t *) 0xe000e010 ))                  /* SysTick 控制寄存器 */
#define portNVIC_SYSTICK_LOAD_REG (*((volatile uint32_t *) 0xe000e014 ))                  /* SysTick 重装载寄存器寄存器 */

#ifndef configSYSTICK_CLOCK_HZ                                                            /* SysTick 时钟源选择 */
  #define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
  #define portNVIC_SYSTICK_CLK_BIT  ( 1UL << 2UL )
#else
  #define portNVIC_SYSTICK_CLK_BIT  ( 0 )
#endif

#define portNVIC_SYSTICK_INT_BIT     ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT  ( 1UL << 0UL )

/*
*************************************************************************
*                               函数声明
*************************************************************************
*/

extern __asm void prvStartFirstTask( void );
void vPortSetupTimerInterrupt( void );

/*
*************************************************************************
*                               全局变量
*************************************************************************
*/

/*
*************************************************************************
*                                函数定义
*************************************************************************
*/

static void prvTaskExitError( void )
{
    /* 函数停止在这里 */
    for(;;);
}


StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack,    /* 对齐后的栈顶指针 */
                                    TaskFunction_t pxCode,        /* 函数地址 */
									                  void *pvParameters)           /* 任务形参数 */
{
	/* 恢复运行时, 自动加载到CPU寄存器中的内容 */
	/* xPSR 的位 24, 必须置1, 即 0x01000000*/
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_XPSR;

	/* 函数地址 */
	pxTopOfStack--;
	*pxTopOfStack =  ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;

	/* prvTaskExitError的函数地址: 如果返回跳转到了这个函数, 则是一个无限循环 */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;
	
	/* R12, R3, R2 and R1 默认初始化为 0 */
	pxTopOfStack -= 5;

	/* CPU 第一次运行任务的时候, 将这 8 个寄存器加载到 CPU */
	/* R4、R5、R6、R7、R8、R9、R10和 R11，默认初始化为 0 */
	pxTopOfStack -= 8;

	/* 返回栈顶指针 */
	return pxTopOfStack;
}


BaseType_t xPortStartScheduler( void )
{
	/* 配置 PendSV 和 SysTick 的中断优先级为最低 */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
  
	/* 初始化 sysTick 中断 */
  vPortSetupTimerInterrupt();
	
  /* 启动第一个任务，不再返回 */
  prvStartFirstTask();
  
  /* 不应该运行到这里 */
  return 0;
}


/*
 *  参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3，百度搜索“PM0056”即可找到这个文档
 *  在 Cortex-M 中，内核外设SCB 的地址范围为：0xE000ED00-0xE000ED3F
 *  0xE000ED008 为 SCB 外设中 SCB_VTOR 这个寄存器的地址，里面存放的是向量表的起始地址，即 MSP 的地址
 */
__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* 将 0xE000ED08 这 32 位数据写入 r0 寄存器,  0xE000ED08 是 SCB_VTOR 这个寄存器的地址, 里面存放的是 
	   里面存放的是向量表的起始地址，即 MSP 的地址 */
	ldr r0, =0xE000ED08
	ldr r0, [r0]
	ldr r0, [r0]
	
	/* msp 指针 */
	msr msp, r0

	/* 
		使用 CPS 指令把全局中断打开。为了快速地开关中断， Cortex-M 内核 专门设置了一条 CPS 指令，有 4 种用法 
		CPSID I ;PRIMASK=1 ;关中断
		CPSIE I ;PRIMASK=0 ;开中断
		CPSID F ;FAULTMASK=1 ;关异常
		CPSIE F ;FAULTMASK=0 ;开
	*/
	cpsie i
	cpsie f
	dsb
	isb

	/* 调用 SVC 去启动第一个任务, 产生系统调用，服务号 0表示 SVC 中断，接下来将会执行 SVC 中 断服务函数。 */
	svc 0
	nop
	nop
}

__asm void vPortSVCHandler( void )
{
	/* 当前正在运行的 TCB */
	extern pxCurrentTCB;
	
	/* 8 字节对齐 */
	PRESERVE8

	/* 栈顶指针的地址加载到寄存器 r3 中*/
	ldr r3, =pxCurrentTCB

	/* 栈顶指针加载寄存器 r1 */
	ldr r1, [r3]
	
	/* 栈顶第一个字内容加载到 r0 */
	ldr r0, [r1]

	/* 从 r0 指向的地址, 顺着加载 8 个字 到 r4 ~ r11 寄存器*/
	ldmia r0!, {r4-r11}
	
	/* 将 新的栈顶指针加载到 psp 寄存器中 */
	msr psp, r0

	/*  刷新指令流水 */
	isb

	/* 将寄存器 r0 清 0 */
	mov r0, #0

	/* 将 basepri 置为0，表示打开所有中断。basepri 是一个中断屏蔽寄存器，大于等于此寄存器值的中断都将被屏蔽 */
	msr basepri, r0
	
	/* 当从 SVC 中断服务退出前，通过向 r14 寄存器最后 4 位按位或上 0x0D，使得硬件在退出时使用进程堆栈指针 PSP 
	   完成出栈操作并返回后进入任务模式、返回 Thumb 状态。在 SVC 中断服务里面，使用的是 MSP 堆栈指针，是处在ARM 状态。 */
	orr r14, #0xd

	/* 异常返回, 这个时候出栈使用的是 PSP 指针，自动将栈中的剩下 内容加载到 CPU 寄存器： xPSR，PC（任务入口地址），R14，R12，R3，R2，R1，R0 （任务的形参）同时 PSP 的值也将更新，即指向任务栈的起始地址了 */
	bx r14
}

/* pendSV 原任务先入栈(寄存器 -> 栈), 修改 sp, 新任务出栈(栈 -> 寄存器) */
__asm void xPortPendSVHandler()
{
  /* task.c 中定义的当前正在执行的任务, TCB_t * 类型 */
  /* 这个当前正在执行的任务表示需要换出的任务 */
  extern pxCurrentTCB; 
  
  /* 声明外部函数 vTaskSwitchContext，等下会用到 */
  extern vTaskSwitchContext;
  
  /* 前栈需按照 8字节对齐，如果都是 32位的操作则 4 个字节对齐即可。在 Cortex-M 中浮点运算是 8字节的。 */
  PRESERVE8
  
  /* 
     将 PSP 的值存储到 r0, 当 进入 PendSV Handler 时, 上一个任务的运行环境即: xPSR, PC(任务入口地址), R14, R12, R3, R2, R1, R0(任务形参) 会自动存储到 PSP 
     指向的堆栈指针中。
     
     此时 psp 当中栈顶指针已经指向了需要手动加载的 r4-r11 的内存地址.
  */
  mrs r0, psp
  isb
    
  /* 加载 pxCurrentTCB的地址到 r3。 */
  ldr r3, =pxCurrentTCB
    
  /* 加载 r3指向的内容到 r2，即 r2 等于 pxCurrentTCB. */
  ldr r2, [r3]
    
  /* 以 r0 作为基址（指针先递减，再操作，STMDB 的 DB 表示 Decrease Befor）, 将 CPU寄存器 r4~r11 的值存储到任务栈，同时更新 r0的值 */
  /* 此时 r0 的值指向 R0 任务形参位置*/
  stmdb r0!, {r4-r11}
  
  /* 将 r0 的内容存储 r2 指向的内存地址, 即 pxCurrentTCB 的首个字 : 即更新 PCB 进程控制块的当中的栈顶指针位置 */
  /* R0 指向 R4 寄存器的位置  */
  str r0, [r2]
  
  // ========================================== 至此上一个运行任务的入栈操作完成 ==================================
    
  /* 在整个系统中, 中断使用的是主堆栈, sp 指向的是主堆栈, 因此 此指令是将 r3 和 r14 压入主堆栈*/
  /* 原先 r14 的内容当中存储的内容，PendSV 中断退出的时候需要根据 R14 的值来决定返回处理器模式还是任务模式，出栈时使用的是 PSP 还是 MSP） */
  /* 后续调用 vTaskSwitchContext 函数的时候, 返回地址会自动保存到 r14, 会覆盖掉 r14, 因此在需要一个地方保存下原先 r14 寄存器中的内容 */
  stmdb sp!, {r3, r14}
  
  // ========================================== 关中断 ==================================
  
  /* 关中断, configMAX_SYSCALL_INTERRUPT_PRIORITY 的值存储到r0，该宏在 FreeRTOSConfig.h 中定义，用来配置中断屏蔽寄存器 BASEPRI 的值，高四位 */
  /* 有效, 目前配置为 191，因为是高四位有效，所以实际值等于 11，即优先级高于或者等于 11 的中断都将被屏蔽。 */
  /*
    在关中断方面，FreeRTOS 与其它的 RTOS 关中断不同，而是操作 BASEPRI 寄存器来预留一部分中断，并不像 μC/OS 或者 RT-Thread 那样直接操作 PRIMASK 
    把所有中断都关闭掉（除了硬 FAULT）
  */
  /*
       数字越小,级别越高, 因此是屏蔽了 <=11 的高优先级任务
  */
  /*
       关闭中断后就进入了临界段, 
  */
  mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
  msr basepri, r0
  dsb
  isb
    
  
  // ========================================== 调用 vTaskSwitchContext 函数 ==================================
  bl vTaskSwitchContext
  
  // ========================================== 开中断 ==================================
  // <= 0 的中断全部屏蔽
  mov r0, #0
  msr basepri, r0
  
  // ========================================== 函数已经调用完毕, 从 MSP 中恢复 r14 和 r3 到寄存器 ==================================
  /* 从主堆栈中恢复寄存器 r3 和 r14 的值，此时的 sp 使用的是 MSP */
  ldmia sp!, {r3, r14}
  
  // ========================================== 函数已经调用完毕, 从 MSP 中恢复 r14 和 r3 到寄存器 ==================================
  /* vTaskSwitchContext 调用后  pxCurrentTCB 指针内容已经指向 新的任务栈了, 此时 r1 存储的是新 TCB 的地址*/
  ldr r1, [r3]
  
  /* 取 TCB 结构体第一个字, 即栈顶指针给 r0*/
  ldr r0, [r1]
    
  /* 手动将栈存储 r4~r11 存储到 r4~r11 中*/
  ldmia r0!, {r4-r11}
  
  /* 更新 psp 当中的栈顶指针, 此时 psp 指向新栈当中 CPU 自动加载的位置*/
  msr psp, r0
  
  /* 指令屏障 */
  isb
    
  /* R14 中保存异常返回标志，包括返回后进入任务模 式还是处理器模式、使用 PSP 堆栈指针还是 MSP 堆栈指针。 */
  /* 此时的 r14 的值为  0xfffffffd,  表示异常返回后进入任务模式，SP 以 PSP 作为堆栈指针出栈，出栈完毕后 PSP 指向任务栈的栈顶*/
  /* 
     当调用 bx r14 指令后，系统以 PSP 作为 SP 指针出栈，把接下来要运行的新任务的任务栈中剩下的内容加载到 CPU 寄存器：R0（任务形参）、R1、R2、R3、R12、R14       （LR）、R15（PC）和 xPSR，从而切换到新的任务。 
  */
  bx r14
  nop
}

// systick 中断服务函数
void xPortSysTickHandler( void )
{
  /* 关中断 */
  vPortRaiseBASEPRI();
	
  /* 更新系统时基 */
  xTaskIncrementTick();
	
  /* 开中断 */
	vPortClearBASEPRIFromISR();
}


void vPortSetupTimerInterrupt( void )
{
	portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
	portNVIC_SYSTICK_CTRL_REG |= ( portNVIC_SYSTICK_CLK_BIT |
                                 portNVIC_SYSTICK_INT_BIT |
                                 portNVIC_SYSTICK_ENABLE_BIT );
}


/* 进入临界区: 非中断 */
void vPortEnterCritical( void )
{
  /* 关中断 */
  portDISABLE_INTERRUPTS();
  
  /* uxCriticalNesting是在 port.c 中定义的静态变量，表示临界段嵌套计 数 器 ， 默 认 初 始 化 为 0xaaaaaaaa */
  /* 在 调 度 器 启 动 时 会 被 重 新 初 始 化 为 0 */
  uxCriticalNesting++;
  
  /* 如果uxCriticalNesting等于 1，即一层嵌套，要确保当前没有中断活跃，即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0 */
  if ( uxCriticalNesting == 1 )
  {
      /*要确保当前没有中断活跃，即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0 */
      // configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
  }
}

/* 退出临界区: 非中断 */
void vPortExitCritical( void )
{
  // configASSERT( uxCriticalNesting );
  
  uxCriticalNesting--;
  
  if ( uxCriticalNesting == 0 )
  {
     /* 开中断 */
     portENABLE_INTERRUPTS();
  }
}
